package com.collins.thread.lock.cas;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;

public class LongAdderAndAccumulatorTest {

    private static final int THREAD_CNT = 50;
    private static final int ITER_CNT = 10000 * 100;
    private static Long startTime;
    private static Long endTime;
    private static CountDownLatch latch1 = new CountDownLatch(THREAD_CNT);
    private static CountDownLatch latch2 = new CountDownLatch(THREAD_CNT);
    private static CountDownLatch latch3 = new CountDownLatch(THREAD_CNT);
    private static CountDownLatch latch4 = new CountDownLatch(THREAD_CNT);

    public static void main(String[] args) {
        // TODO Synchronized add
        startTime = System.currentTimeMillis();
        for (int i = 0; i < THREAD_CNT; i++) {
            try {
                new Thread(()->{
                    for (int j = 0; j < ITER_CNT; j++) {
                        AddFunction.synchronizeAdd();
                    }
                }).start();
            } finally {
                latch1.countDown();
            }
        }
        try {
            latch1.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        endTime = System.currentTimeMillis();
        System.out.println("synchronized method - total time: " + (endTime - startTime) + "ms");

        // TODO atomic long add
        startTime = System.currentTimeMillis();
        for (int i = 0; i < THREAD_CNT; i++) {
            try {
                new Thread(()->{
                    for (int j = 0; j < ITER_CNT; j++) {
                        AddFunction.atomicIntegerAdd();
                    }
                }).start();
            } finally {
                latch2.countDown();
            }
        }
        try {
            latch2.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        endTime = System.currentTimeMillis();
        System.out.println("atmoic long method - total time: " + (endTime - startTime) + "ms");

        // TODO long adder add
        startTime = System.currentTimeMillis();
        for (int i = 0; i < THREAD_CNT; i++) {
            try {
                new Thread(()->{
                    for (int j = 0; j < ITER_CNT; j++) {
                        AddFunction.longAdderAdd();
                    }
                }).start();
            } finally {
                latch3.countDown();
            }
        }
        try {
            latch3.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        endTime = System.currentTimeMillis();
        System.out.println("long adder method - total time: " + (endTime - startTime) + "ms");

        // TODO long accumulator add
        startTime = System.currentTimeMillis();
        for (int i = 0; i < THREAD_CNT; i++) {
            try {
                new Thread(()->{
                    for (int j = 0; j < ITER_CNT; j++) {
                        AddFunction.longAccumulatorAdd();
                    }
                }).start();
            } finally {
                latch4.countDown();
            }
        }
        try {
            latch4.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        endTime = System.currentTimeMillis();
        System.out.println("long accumulator method - total time: " + (endTime - startTime) + "ms");
    }

}

class AddFunction {

    public static int number = 0;
    public static AtomicLong atomicLong = new AtomicLong(0);
    public static LongAdder longAdder = new LongAdder();
    public static LongAccumulator longAccumulator = new LongAccumulator(Long::sum, 0);

    public static synchronized void synchronizeAdd() {
        number++;
    }

    public static void atomicIntegerAdd() {
        atomicLong.getAndIncrement();
    }

    public static void longAdderAdd() {
        longAdder.increment();
    }

    public static void longAccumulatorAdd() {
        longAccumulator.accumulate(1);
    }

}
